package com.yupi.yuaicodemother.ai;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.yupi.yuaicodemother.service.ChatHistoryService;
import dev.langchain4j.community.store.memory.chat.redis.RedisChatMemoryStore;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.service.AiServices;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

@Configuration
@Slf4j
public class AIProcessCodeServiceFactory {

    @Resource
    private ChatModel chatModel;

    @Resource
    private StreamingChatModel streamingChatModel;

    @Resource
    private RedisChatMemoryStore redisChatMemoryStore;

    @Resource
    private ChatHistoryService chatHistoryService;

    /**
     * AI 服务实例本地缓存
     * 缓存策略说明：
     * - maximumSize(1000)：最多缓存 1000 个实例，防止内存溢出
     * - expireAfterWrite(30min)：写入后 30 分钟过期，防止长期占用内存
     * - expireAfterAccess(10min)：访问后 10 分钟过期，提升热点命中率
     * - removalListener：记录实例移除日志，便于调试
     */
    // 本地内存缓存：key 为应用 ID（appId），value 为对应的 AI 服务实例
// 使用 Caffeine 高性能缓存框架，支持并发读写、自动失效、事件监听等特性
    private final Cache<Long, AIProcessCodeService> serviceCache = Caffeine.newBuilder()
            // 缓存容量：最多同时保留 1000 个 AI 服务实例
            // 当超过上限时，Caffeine 会基于 LRU 策略自动移除最近最少使用的实例
            .maximumSize(1000)

            // 写后过期策略：实例写入缓存后，30 分钟即失效
            // 适用于“生成一次后，长时间不再使用”的场景，防止冷数据长期占用内存
            .expireAfterWrite(Duration.ofMinutes(30))

            // 读后过期策略：最后一次访问后，超过 10 分钟即失效
            // 适用于“热点数据”场景，保证活跃实例常驻，冷数据快速淘汰
            .expireAfterAccess(Duration.ofMinutes(10))

            // 移除监听器：当实例被驱逐、过期或手动删除时触发
            // 可用于监控缓存命中率、排查内存泄漏、记录审计日志
            .removalListener((key, value, cause) -> {
                // DEBUG 级别日志：记录被移除的 appId 及移除原因
                log.debug("AI 服务实例被移除，appId: {}, 原因: {}", key, cause);
            })
            // 最终构建出线程安全、高性能的缓存实例
            .build();

    /**
     * 根据 appId 获取对应的 AI 服务实例
     * 若缓存中存在则直接返回；否则调用 createAIProcessCodeService 创建新实例
     *
     * @param appId 应用唯一标识
     * @return 对应的 AI 代码生成服务实例
     */
    public AIProcessCodeService getAIProcessCodeService(long appId) {
        return serviceCache.get(appId, this::createAIProcessCodeService);
    }

    /**
     * 创建新的 AI 服务实例
     * 每个实例拥有独立的对话记忆（Memory），确保不同应用之间上下文隔离
     *
     * @param appId 应用唯一标识
     * @return 新创建的 AI 服务实例
     */
    /**
     * 为指定 appId 创建一个全新的 AI 代码生成器服务实例。
     * 核心目标：让“每一个应用”都拥有完全隔离的上下文（对话历史 + AI 模型绑定）。
     * 实现思路：LangChain4j 的 MessageWindowChatMemory 天然支持“按 id 隔离”，
     * 再叠加 RedisChatMemoryStore 做持久化，即可在多实例、多线程环境下
     * 保证“同一个应用看到的永远是自己的对话历史”。
     */
    private AIProcessCodeService createAIProcessCodeService(long appId) {
        // 1. 打印一条 info 级别日志，方便在灰度、压测或排障阶段
        //    直接 grep "为 appId: xxx 创建新的 AI 服务实例" 即可定位实例化时机。
        log.info("为 appId: {} 创建新的 AI 服务实例", appId);

        /* ----------------------------------------------------------
         * 2. 构建“对话记忆”对象
         *    - id(appId)                 : 以 appId 作为唯一标识，实现“租户级”隔离。
         *                                  不同 appId 对应的 ChatMemory 在 Redis 中的 key 也不同，
         *                                  从而彻底避免串台。
         *    - chatMemoryStore(...)      : 把对话历史持久化到 Redis。
         *                                  即使应用重启 / 多节点部署，也能恢复上下文。
         *    - maxMessages(20)           : 只保留最近 20 条消息，防止 token 无限增长、
         *                                  节约内存与 Redis 空间，同时也是 LLM 上下文长度
         *                                  控制的最后一道保险。
         * ---------------------------------------------------------- */
        MessageWindowChatMemory chatMemory = MessageWindowChatMemory
                .builder()
                .id(appId)
                .chatMemoryStore(redisChatMemoryStore)
                .maxMessages(20)
                .build();

        /* ----------------------------------------------------------
         * 3. 从业务数据库（MySQL）加载“离线”的对话历史
         *    场景说明：
         *      - 用户可能把应用分享到别处，或服务器重启后需要“续聊”。
         *      - chatHistoryService 会把数据库中最新的 20 条记录反序列化，
         *        并注入到 LangChain4j 的 ChatMemory 中，实现“断点续传”。
         *    注意：这里只加载 20 条，与 maxMessages 保持一致，避免
         *          内存浪费，也防止 Redis 与 DB 数据不一致。
         * ---------------------------------------------------------- */
        chatHistoryService.loadChatHistoryToMemory(appId, chatMemory, 20);

        /* ----------------------------------------------------------
         * 4. 使用 LangChain4j 的 AiServices 动态代理机制
         *    创建接口 AIProcessCodeService 的实现类。
         *    关键点：
         *      - chatModel           : 同步调用（阻塞式），用于“一次性”生成代码。
         *      - streamingChatModel  : 流式调用（Reactive），用于 Server-Sent Events。
         *      - chatMemory          : 把上面准备好的记忆绑定到该实例，
         *                              后续所有调用都会自动读写 Redis。
         *    最终返回的实例是线程安全的，可被 ConcurrentHashMap 或 Caffeine
         *    缓存，供同一个 appId 反复使用。
         * ---------------------------------------------------------- */
        return AiServices.builder(AIProcessCodeService.class)
                .chatModel(chatModel)
                .streamingChatModel(streamingChatModel)
                .chatMemory(chatMemory)
                .build();
    }

    /**
     * Spring 容器启动时默认创建一个通用 AI 服务实例（appId = 0）
     * 适用于无 appId 的场景（如测试或全局调用）
     *
     * @return 默认 AI 服务实例
     */
    @Bean
    public AIProcessCodeService AIProcessCodeService() {
        return getAIProcessCodeService(0);
    }
}